/**
 * 
 */
package mdp.solve;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;

import dd.discrete.DD;

import util.Pair;
import util.Timer;

import mdp.id.ADDHandler;
import mdp.id.RDDL2DD;
import mdp.id.RDDL2DD.DEBUG;
import mdp.id.RDDL2DD.ORDER;

/**
 * @author NR
 *
 * TODO
 */
public class ValueIteration<T extends RDDL2DD> implements Runnable{

	private static double	EPSILON	= 0;
	private T _handler;
	private ArrayBlockingQueue<Pair<T, Integer>> _bq = null;
	/**
	 * 
	 */
	public ValueIteration(T handler, String domain, String instance, double epsilon,
			ArrayBlockingQueue<Pair<T, Integer>> bq) {
		_bq = bq;
		EPSILON = epsilon;
		_handler = handler;
		_handler.buildFromFile(domain, instance);
	}

	/* (non-Javadoc)
	 * @see java.lang.Runnable#run()
	 */
//	@Override
	public void run() {
		
		int _valueDD = _handler.DD_ZERO;
		int _policy = _handler.DD_ZERO;
		
		int iter = 0;
		boolean done = false;
		boolean lastiter = false;

		Timer cptTimer = new Timer();
		ArrayList<Map<Integer, Boolean>> regOrder = _handler.convertToBN(null,null,true);
		cptTimer.StopTimer();
		
		Timer solutionTimer = new Timer();

		while( !done ) {

			solutionTimer.ResumeTimer();
			
			int Vprev = _valueDD;
			_handler.saveThis(Vprev);
			int Vprimed = _handler.swapForPrimes(_valueDD);
			_handler.saveThis(Vprimed);

			for( Map<Integer, Boolean> jointAct : regOrder  ) {
				
//				System.out.println(jointAct);
				
				Pair<Integer, Integer> vq = _handler.regress(Vprimed, jointAct, true, 
						 _valueDD, _policy);
				
				if( _handler._debug.compareTo(DEBUG.SOLUTION_INFO) >= 0 ) {
					System.out.println("leaf node " + _handler.getSize(vq._o1) );
				}
				
				int Vnext = _handler._context.applyInt(_valueDD, vq._o1, DD.ARITH_MAX);
				
				if( lastiter ) {
					int diff = _handler._context.applyInt(Vnext, vq._o1, DD.ARITH_MINUS);
					
					int actzero = _handler.thresholdAt(diff, 0.0d);
					
					int actone = _handler._context.applyInt(_handler.DD_ONE, actzero
							, DD.ARITH_MINUS); 
					
					long actid = _handler.getJointActionID(jointAct);
					
					_policy = _handler._context.applyInt(
							_handler._context.applyInt(_policy, actzero, DD.ARITH_PROD)
							,_handler._context.scalarMultiply(actone, actid), 
							DD.ARITH_SUM);
				}
				
				
				_valueDD = Vnext;
				
				_handler.flushCaches(true, _valueDD, _policy);
				
				
			}
//			System.out.println(_handler.getSize(_valueDD)); 
			
			int diff = _handler._context.applyInt(_valueDD, Vprev, DD.ARITH_MINUS);
			
			double error = Math.max(
					Math.abs(_handler.getMax(diff)), 
					Math.abs(_handler.getMin(diff) == _handler.getNegInf() ? 0 : _handler.getMin(diff)));
			++iter;
			
			if( lastiter ) {
				done = true;
			}
			
			if( error < EPSILON || _handler.isHorizon(iter+1) ) {
				lastiter = true;
			}
			
			_handler.flushCaches(true, _valueDD, _policy);
			
			_handler.forgetThis(Vprev);
			_handler.forgetThis(Vprimed);
			
			solutionTimer.PauseTimer();

			System.out.println("Iteration " + iter + " " + error + " " + solutionTimer.GetElapsedTimeInMinutes() + " " + _handler.getSize(_valueDD));
//			_handler._context.
//			_handler._context.showCacheSize();
//			System.out.println(_handler._context.applyHit);
			
		}
//		System.out.println(timetosolve);
		
		_handler.setValueDD(_valueDD);
		_handler.setPolicyDD(_policy);
		
		ArrayList stats = new ArrayList();
		try{
			stats = _handler.playDD(_policy);
		}catch(Exception e) {
			e.printStackTrace();
		}
		
		stats.add(cptTimer.GetElapsedTimeInMinutes());
		stats.add(solutionTimer.GetElapsedTimeInMinutes());
		stats.add(_handler.getSize(_valueDD));
		stats.add(_handler.getSize(_policy));
		
		System.out.println(stats);
//		_handler.showGraph(_policy);
		
		if( _bq != null ) {
			try
			{
				_bq.put(new Pair<T, Integer>(_handler, _valueDD));
//				_bq.put(new Pair<T, Integer>(_handler, _policy));
			} catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		
		}
		
		
	}
	
	public static void main(String[] args) throws InterruptedException {
		ADDHandler handle = new ADDHandler(DEBUG.PROBLEM_INFO , ORDER.GUESS);
		Runnable vi = 
			new ValueIteration<ADDHandler>(handle, args[0], args[1], 
					Double.parseDouble(args[2]), null);
		Thread viWorker = new Thread(vi, "VI");
		viWorker.start();
		viWorker.join();
	}
	

}
